﻿using System;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using System.Drawing;
using System.Collections.ObjectModel;

namespace MMOG
{
    public class OpenGLControl : System.Windows.Forms.Control
    {
        public const uint GL_MATRIX_MODE = 0x0BA0;
        public const uint GL_MODELVIEW = 0x1700;
        public const uint GL_PROJECTION = 0x1701;
        public const uint GL_TEXTURE = 0x1702;

        public const uint GL_BLEND = 0x0BE2;
        public const uint GL_BLEND_SRC = 0x0BE1;
        public const uint GL_BLEND_DST = 0x0BE0;
        public const uint GL_ZERO = 0x0;

        public const uint GL_ONE = 0x1;
        public const uint GL_SRC_COLOR = 0x0300;
        public const uint GL_ONE_MINUS_SRC_COLOR = 0x0301;
        public const uint GL_SRC_ALPHA = 0x0302;
        public const uint GL_ONE_MINUS_SRC_ALPHA = 0x0303;
        public const uint GL_DST_ALPHA = 0x0304;
        public const uint GL_ONE_MINUS_DST_ALPHA = 0x0305;
        public const uint GL_DST_COLOR = 0x0306;
        public const uint GL_ONE_MINUS_DST_COLOR = 0x0307;
        public const uint GL_SRC_ALPHA_SATURATE = 0x0308;
        public const uint GL_CONSTANT_COLOR = 0x8001;
        public const uint GL_ONE_MINUS_CONSTANT_COLOR = 0x8002;
        public const uint GL_CONSTANT_ALPHA = 0x8003;
        public const uint GL_ONE_MINUS_CONSTANT_ALPHA = 0x8004;

        public const uint GL_BYTE = 0x1400;
        public const uint GL_UNSIGNED_BYTE = 0x1401;
        public const uint GL_SHORT = 0x1402;
        public const uint GL_UNSIGNED_SHORT = 0x1403;
        public const uint GL_INT = 0x1404;
        public const uint GL_UNSIGNED_INT = 0x1405;
        public const uint GL_FLOAT = 0x1406;
        public const uint GL_DOUBLE = 0x140A;
        public const uint GL_2_BYTES = 0x1407;
        public const uint GL_3_BYTES = 0x1408;
        public const uint GL_4_BYTES = 0x1409;

        public const uint GL_NONE = 0x0;
        public const uint GL_LEFT = 0x0406;
        public const uint GL_RIGHT = 0x0407;
        public const uint GL_FRONT_LEFT = 0x0400;
        public const uint GL_FRONT_RIGHT = 0x0401;
        public const uint GL_BACK_LEFT = 0x0402;
        public const uint GL_BACK_RIGHT = 0x0403;
        public const uint GL_AUX0 = 0x0409;
        public const uint GL_AUX1 = 0x040A;
        public const uint GL_AUX2 = 0x040B;
        public const uint GL_AUX3 = 0x040C;
        public const uint GL_COLOR_INDEX = 0x1900;
        public const uint GL_RED = 0x1903;
        public const uint GL_GREEN = 0x1904;
        public const uint GL_BLUE = 0x1905;
        public const uint GL_ALPHA = 0x1906;
        public const uint GL_LUMINANCE = 0x1909;
        public const uint GL_LUMINANCE_ALPHA = 0x190A;
        public const uint GL_ALPHA_BITS = 0x0D55;
        public const uint GL_RED_BITS = 0x0D52;
        public const uint GL_GREEN_BITS = 0x0D53;
        public const uint GL_BLUE_BITS = 0x0D54;
        public const uint GL_INDEX_BITS = 0x0D51;
        public const uint GL_SUBPIXEL_BITS = 0x0D50;
        public const uint GL_AUX_BUFFERS = 0x0C00;
        public const uint GL_READ_BUFFER = 0x0C02;
        public const uint GL_DRAW_BUFFER = 0x0C01;
        public const uint GL_DOUBLEBUFFER = 0x0C32;
        public const uint GL_STEREO = 0x0C33;
        public const uint GL_BITMAP = 0x1A00;
        public const uint GL_COLOR = 0x1800;
        public const uint GL_DEPTH = 0x1801;
        public const uint GL_STENCIL = 0x1802;
        public const uint GL_DITHER = 0x0BD0;
        public const uint GL_RGB = 0x1907;
        public const uint GL_RGBA = 0x1908;

        public const uint GL_PERSPECTIVE_CORRECTION_HINT = 0x0C50;
        public const uint GL_POINT_SMOOTH_HINT = 0x0C51;
        public const uint GL_LINE_SMOOTH_HINT = 0x0C52;
        public const uint GL_POLYGON_SMOOTH_HINT = 0x0C53;

        public const uint GL_DONT_CARE = 0x1100;
        public const uint GL_FASTEST = 0x1101;
        public const uint GL_NICEST = 0x1102;

        public const uint GL_UNPACK_ALIGNMENT = 0x0CF5;

        public const uint GL_TEXTURE_2D = 0x0DE1;

        public const uint GL_TEXTURE_MAG_FILTER = 0x2800;
        public const uint GL_TEXTURE_MIN_FILTER = 0x2801;
        public const uint GL_TEXTURE_WRAP_S = 0x2802;
        public const uint GL_TEXTURE_WRAP_T = 0x2803;

        public const uint GL_NEAREST = 0x2600;
        public const uint GL_LINEAR = 0x2601;
        public const int GL_NEAREST_MIPMAP_NEAREST = 0x2700;
        public const int GL_LINEAR_MIPMAP_NEAREST = 0x2701;
        public const int GL_NEAREST_MIPMAP_LINEAR = 0x2702;
        public const int GL_LINEAR_MIPMAP_LINEAR = 0x2703;
        public const int GL_CLAMP = 0x2900;
        public const int GL_REPEAT = 0x2901;

        public const uint GL_TEXTURE_ENV_MODE = 0x2200;
        public const uint GL_TEXTURE_ENV_COLOR = 0x2201;
        public const uint GL_TEXTURE_ENV = 0x2300;

        public const uint GL_DECAL = 0x2101;

        public const uint GL_DEPTH_TEST = 0x0B71;

        public const uint GL_POINT_SMOOTH = 0x0B10;
        public const uint GL_LINE_SMOOTH = 0x0B20;
        public const uint GL_POLYGON_SMOOTH = 0x0B41;

        // Layer types
        public enum LayerTypes : sbyte
        {
            PFD_MAIN_PLANE = 0,
            PFD_OVERLAY_PLANE = 1,
            PFD_UNDERLAY_PLANE = -1,
        }

        // Pixel types
        public enum PixelTypes : byte
        {
            PFD_TYPE_RGBA = 0,
            PFD_TYPE_COLORINDEX = 1,
        }

        // PIXELFORMATDESCRIPTOR flags
        [Flags]
        public enum PFD_Flags : uint
        {
            PFD_DOUBLEBUFFER = 0x00000001,
            PFD_STEREO = 0x00000002,
            PFD_DRAW_TO_WINDOW = 0x00000004,
            PFD_DRAW_TO_BITMAP = 0x00000008,
            PFD_SUPPORT_GDI = 0x00000010,
            PFD_SUPPORT_OPENGL = 0x00000020,
            PFD_GENERIC_FORMAT = 0x00000040,
            PFD_NEED_PALETTE = 0x00000080,
            PFD_NEED_SYSTEM_PALETTE = 0x00000100,
            PFD_SWAP_EXCHANGE = 0x00000200,
            PFD_SWAP_COPY = 0x00000400,
            PFD_SWAP_LAYER_BUFFERS = 0x00000800,
            PFD_GENERIC_ACCELERATED = 0x00001000,
            PFD_SUPPORT_DIRECTDRAW = 0x00002000,
        }

        // AttribMask
        [Flags]
        public enum AttribMask : uint
        {
            GL_CURRENT_BIT = 0x00000001,
            GL_POINT_BIT = 0x00000002,
            GL_LINE_BIT = 0x00000004,
            GL_POLYGON_BIT = 0x00000008,
            GL_POLYGON_STIPPLE_BIT = 0x00000010,
            GL_PIXEL_MODE_BIT = 0x00000020,
            GL_LIGHTING_BIT = 0x00000040,
            GL_FOG_BIT = 0x00000080,
            GL_DEPTH_BUFFER_BIT = 0x00000100,
            GL_ACCUM_BUFFER_BIT = 0x00000200,
            GL_STENCIL_BUFFER_BIT = 0x00000400,
            GL_VIEWPORT_BIT = 0x00000800,
            GL_TRANSFORM_BIT = 0x00001000,
            GL_ENABLE_BIT = 0x00002000,
            GL_COLOR_BUFFER_BIT = 0x00004000,
            GL_HINT_BIT = 0x00008000,
            GL_EVAL_BIT = 0x00010000,
            GL_LIST_BIT = 0x00020000,
            GL_TEXTURE_BIT = 0x00040000,
            GL_SCISSOR_BIT = 0x00080000,
            GL_ALL_ATTRIB_BITS = 0x000fffff,
        }

            public const uint GL_POINTS = 0x0000;
            public const uint GL_LINES = 0x0001;
            public const uint GL_LINE_LOOP = 0x0002;
            public const uint GL_LINE_STRIP = 0x0003;
            public const uint GL_TRIANGLES = 0x0004;
            public const uint GL_TRIANGLE_STRIP = 0x0005;
            public const uint GL_TRIANGLE_FAN = 0x0006;
            public const uint GL_QUADS = 0x0007;
            public const uint GL_QUAD_STRIP = 0x0008;
            public const uint GL_POLYGON = 0x0009;

        // Structure of PIXELFORMATDESCRIPTOR used by ChoosePixelFormat()
        [StructLayout(LayoutKind.Sequential)]
        public struct PIXELFORMATDESCRIPTOR
        {
            public ushort nSize;
            public ushort nVersion;
            public uint dwFlags;
            public byte iPixelType;
            public byte cColorBits;
            public byte cRedBits;
            public byte cRedShift;
            public byte cGreenBits;
            public byte cGreenShift;
            public byte cBlueBits;
            public byte cBlueShift;
            public byte cAlphaBits;
            public byte cAlphaShift;
            public byte cAccumBits;
            public byte cAccumRedBits;
            public byte cAccumGreenBits;
            public byte cAccumBlueBits;
            public byte cAccumAlphaBits;
            public byte cDepthBits;
            public byte cStencilBits;
            public byte cAuxBuffers;
            public sbyte iLayerType;
            public byte bReserved;
            public uint dwLayerMask;
            public uint dwVisibleMask;
            public uint dwDamageMask;
        }

        // External Win32 libraries
        public const string KER_DLL = "kernel32.dll";	// Import library for Kernel on Win32
        public const string OGL_DLL = "opengl32.dll";	// Import library for OpenGL on Win32
        public const string GLU_DLL = "glu32.dll";	    // Import library for GLU on Win32
        public const string GDI_DLL = "gdi32.dll";		// Import library for GDI on Win32
        public const string USR_DLL = "user32.dll";		// Import library for User on Win32

        // Class variables
        protected uint m_hDC = 0;						// Display device context
        protected uint m_hRC = 0;						// OpenGL rendering context
        protected uint m_hModuleGDI = 0;				// Handle to GDI32.DLL
        protected uint m_hModuleOGL = 0;				// Handle to OPENGL32.DLL

        // Define all of the exported functions from unmanaged Win32 DLLs

        // kernel32.dll unmanaged Win32 DLL
        [DllImport(KER_DLL)]
        public static extern uint LoadLibrary(string lpFileName);

        // opengl32.dll unmanaged Win32 DLL
        [DllImport(OGL_DLL)]
        public static extern uint wglGetCurrentContext();
        [DllImport(OGL_DLL)]
        public static extern uint wglGetProcAddress(char[] name);
        [DllImport(OGL_DLL)]
        public static extern int wglMakeCurrent(uint hdc, uint hglrc);
        [DllImport(OGL_DLL)]
        public static extern uint wglCreateContext(uint hdc);
        [DllImport(OGL_DLL)]
        public static extern int wglDeleteContext(uint hglrc);
        [DllImport(OGL_DLL)]
        public static extern void glClearColor(float red, float green, float blue, float alpha);
        [DllImport(OGL_DLL)]
        public static extern void glClear(AttribMask mask);
        [DllImport(OGL_DLL)]
        public static extern void glColor3f(float red, float green, float blue);
        [DllImport(OGL_DLL)]
        public static extern void glColor4f(float red, float green, float blue, float alpha);
        [DllImport(OGL_DLL)]
        public static extern void glOrtho(double left, double right, double bottom, double top, double zNear, double zFar);
        [DllImport(OGL_DLL)]
        public static extern void glFrustum(double left, double right, double bottom, double top, double zNear, double zFar);
        [DllImport(OGL_DLL)]
        public static extern void glBegin(uint mode);
        [DllImport(OGL_DLL)]
        public static extern void glVertex2f(float x, float y);
        [DllImport(OGL_DLL)]
        public static extern void glVertex3f(float x, float y, float z);
        [DllImport(OGL_DLL)]
        public static extern void glNormal3f (float nx, float ny, float nz);
        [DllImport(OGL_DLL)]
        public static extern void glTexCoord2f(float s, float t);
        [DllImport(OGL_DLL)]
        public static extern void glTranslatef(float x, float y, float z);
        [DllImport(OGL_DLL)]
        public static extern void glRotatef(float angle, float x, float y, float z);
        [DllImport(OGL_DLL)]
        public static extern void glEnd();
        [DllImport(OGL_DLL)]
        public static extern void glFlush();
        [DllImport(OGL_DLL)]
        public static extern void glViewport(int x, int y, int width, int height);
        [DllImport(OGL_DLL)]
        protected static extern void glShadeModel(uint mode);
        [DllImport(OGL_DLL)]
        public static unsafe extern void glDrawPixels(int width, int height, uint format, uint type, byte* pixel);
        [DllImport(OGL_DLL)]
        public static extern void glRasterPos2f(float x, float y);
        [DllImport(OGL_DLL)]
        public static extern void glHint(uint target, uint mode);
        [DllImport(OGL_DLL)]
        public static extern void glBlendFunc(uint sfactor, uint dfactor);
        [DllImport(OGL_DLL)]
        public static extern void glEnable(uint cap);
        [DllImport(OGL_DLL)]
        public static extern void glDisable(uint cap);
        [DllImport(OGL_DLL)]
        public static extern void glLoadIdentity();
        [DllImport(OGL_DLL)]
        public static extern void glMatrixMode(uint mode);
        [DllImport(OGL_DLL)]
        public static unsafe extern void glGenTextures(int n, uint* textures);
        [DllImport(OGL_DLL)]
        public static extern void glBindTexture(uint target, uint texture);
        [DllImport(OGL_DLL)]
        public static extern void glPixelStorei(uint pname, int param);
        [DllImport(OGL_DLL)]
        public static extern void glTexParameteri(uint target, uint pname, int param);
        [DllImport(OGL_DLL)]
        public static extern void glTexEnvf(uint target, uint pname, float param);
        [DllImport(OGL_DLL)]
        public static extern void glPushMatrix();
        [DllImport(OGL_DLL)]
        public static extern void glPopMatrix();
        [DllImport(OGL_DLL)]
        public static extern void glLineWidth(float width);

        // gdi32.dll unmanaged Win32 DLL
        [DllImport(GDI_DLL)]
        public unsafe static extern int ChoosePixelFormat(uint hdc, PIXELFORMATDESCRIPTOR* ppfd);
        [DllImport(GDI_DLL)]
        public unsafe static extern int SetPixelFormat(uint hdc, int iPixelFormat, PIXELFORMATDESCRIPTOR* ppfd);
        [DllImport(GDI_DLL)]
        public static extern int SwapBuffers(uint hdc);

        // user32.dll unmanaged Win32 DLL
        [DllImport(USR_DLL)]
        public static extern uint GetDC(uint hWnd);
        [DllImport(USR_DLL)]
        public static extern int RelaseDC(uint hWnd, uint hDC);

        [DllImport("cdll.dll")]
        public static extern void disableVsync();

        [DllImport(GLU_DLL)]
        public static extern void gluPerspective(double fovy, double aspect, double zNear, double zFar);
        [DllImport(GLU_DLL)]
        public static unsafe extern int gluBuild2DMipmaps(uint target, int components, int width, int height, uint format, uint type, void* data);
        [DllImport(GLU_DLL)]
        public static extern void gluLookAt(double eyex, double eyey, double eyez, double centerx, double centery, double centerz, double upx, double upy, double upz);

        // This method is called when an instance of the control is created
        public OpenGLControl()
        {
            OpenGLInit();
        }

        // This method is automatically called after an object becomes inaccessible
        ~OpenGLControl()
        {
            if (m_hRC != 0)
                wglDeleteContext(m_hRC);
        }

        // This method is called when the control is redrawn
        protected override void OnPaint(PaintEventArgs eventArgs)
        {
            glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
            glClear(AttribMask.GL_COLOR_BUFFER_BIT | AttribMask.GL_DEPTH_BUFFER_BIT);
            glFlush();
            SwapBuffer();
        }

        // This method is called when the Size property of the control is changed
        protected override void OnSizeChanged(EventArgs e)
        {
            //           base.OnSizeChanged(e);
            //           glViewport(0, 0, Width, Height);
        }

        protected unsafe bool OpenGLInit()
        {
            // Explicitly load the OPENGL32.DLL library
            uint m_hModuleOGL = LoadLibrary(OGL_DLL);

            // Retrieve a handle to a display device context for the client area of the specified window
            uint hdc = GetDC((uint)this.Handle);

            // Create a pixel format
            PIXELFORMATDESCRIPTOR pfd;
            pfd.nSize = (ushort)sizeof(PIXELFORMATDESCRIPTOR);
            pfd.nVersion = 1;
            pfd.dwFlags = (uint)(PFD_Flags.PFD_DRAW_TO_WINDOW | PFD_Flags.PFD_SUPPORT_OPENGL | PFD_Flags.PFD_DOUBLEBUFFER);
            pfd.iPixelType = (byte)PixelTypes.PFD_TYPE_RGBA;
            pfd.cColorBits = 24;
            pfd.cRedBits = 0;
            pfd.cRedShift = 0;
            pfd.cGreenBits = 0;
            pfd.cGreenShift = 0;
            pfd.cBlueBits = 0;
            pfd.cBlueShift = 0;
            pfd.cAlphaBits = 0;
            pfd.cAlphaShift = 0;
            pfd.cAccumBits = 0;
            pfd.cAccumRedBits = 0;
            pfd.cAccumGreenBits = 0;
            pfd.cAccumBlueBits = 0;
            pfd.cAccumAlphaBits = 0;
            pfd.cDepthBits = 16;
            pfd.cStencilBits = 0;
            pfd.cAuxBuffers = 0;
            pfd.iLayerType = (sbyte)LayerTypes.PFD_MAIN_PLANE;
            pfd.bReserved = 0;
            pfd.dwLayerMask = 0;
            pfd.dwVisibleMask = 0;
            pfd.dwDamageMask = 0;

            // Match an appropriate pixel format 
            int iPixelformat;
            if ((iPixelformat = ChoosePixelFormat(hdc, &pfd)) == 0)
                return false;

            // Sets the pixel format
            if (SetPixelFormat(hdc, iPixelformat, &pfd) == 0)
                return false;

            // Create a new OpenGL rendering contex
            m_hRC = wglCreateContext(hdc);

            m_hDC = hdc;

            // Make the OpenGL rendering context the current rendering context
            int rv = wglMakeCurrent(m_hDC, m_hRC);

            //wglGetProcAddress("wglSwapIntervalEXT".ToCharArray());
            //disableVsync();

            //glEnable(GL_BLEND);
            glEnable(GL_POINT_SMOOTH);
            glEnable(GL_LINE_SMOOTH);
            glEnable(GL_POLYGON_SMOOTH);
            
            //glOrtho(0, 400, 0, 400, -1, 1);
            glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
            glHint(GL_POINT_SMOOTH_HINT, GL_NICEST);
            glHint(GL_LINE_SMOOTH_HINT, GL_NICEST);
            glHint(GL_POLYGON_SMOOTH_HINT, GL_NICEST);
            //glLineWidth(1.5f);
            return (true);
        }

        public static void setViewport(int width, int height)
        {
            glViewport(0, 0, width, height);
            glMatrixMode(GL_PROJECTION);
            glLoadIdentity();
            glOrtho(0, width - 1, 0, height - 1, 0, 1);            
        }

        public void SwapBuffer()
        {
            // Exchanges the front and back buffers - double buffering
            SwapBuffers(m_hDC);
        }

        public static Collection<String> texNameList = new Collection<String>();
        public static Collection<uint> texNameId = new Collection<uint>();

        public bool loadMtl(string name)
        {
            int id = texNameList.IndexOf(name);
            if (id >= 0)
            {
                glBindTexture(GL_TEXTURE_2D, texNameId[id]);
                return true;
            }                
            uint texName;
            unsafe
            {
                glGenTextures(1, &texName);
                glBindTexture(GL_TEXTURE_2D, texName);
                RawImage img = new RawImage(name, 0xFFFFFFFF);
                fixed (byte* pBuff = img.buff)
                {
                    gluBuild2DMipmaps(GL_TEXTURE_2D, 3, img.width, img.height, GL_RGBA, GL_UNSIGNED_BYTE, pBuff);
                }
            }
            texNameList.Add(name);
            texNameId.Add(texName);
            return true;
        }

        protected override bool IsInputKey(System.Windows.Forms.Keys keyData)
        {
            switch (keyData & Keys.KeyCode)
            {
                case Keys.Up:
                    return true;
                case Keys.Down:
                    return true;
                case Keys.Right:
                    return true;
                case Keys.Left:
                    return true;
                default:
                    return base.IsInputKey(keyData);
            }
        }

        public void drawObject(Object3d obj, float ratio)
        {
            OpenGLControl.glEnable(OpenGLControl.GL_TEXTURE_2D);
            if (obj.material!=null) loadMtl(obj.material);
            glBegin(GL_TRIANGLES);
            glColor4f(1, 1, 1, 1);
            for (int i = 0; i < obj.face.Count; i++)
            {
                VertexInfo[] v = obj.face[i].data;
                for (int j = 0; j < 3; j++)
                {
                    int vv = v[j].v - 1;
                    int vt = v[j].vt - 1;
                    int vn = v[j].vn - 1;
                    glTexCoord2f(obj.textureCoord[vt].x, obj.textureCoord[vt].y);
                    glVertex3f(obj.vertice[vv].x * ratio, obj.vertice[vv].y * ratio, obj.vertice[vv].z * ratio);
                    //glNormal3f();
                }
            }
            glEnd();
        }

        public void drawObjectColor(Object3d obj, float r, float g, float b, float a, float ratio)
        {
            OpenGLControl.glDisable(OpenGLControl.GL_TEXTURE_2D);
            glBegin(GL_TRIANGLES);
            glColor4f(r, g, b, a);
            for (int i = 0; i < obj.face.Count; i++)
            {
                VertexInfo[] v = obj.face[i].data;
                for (int j = 0; j < 3; j++)
                {
                    int vv = v[j].v - 1;
                    glVertex3f(obj.vertice[vv].x * ratio, obj.vertice[vv].y * ratio, obj.vertice[vv].z * ratio);
                }
            }
            glEnd();
        }
    }
}